﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NeturalMath.Properties;
using NeturalMath.Units;

namespace NeturalMath
{
    /// <summary>
    /// Class for holding all built-in System Function methods.  All system functions
    /// defined within NeturalMath should be placed here.
    /// </summary>
    public class SystemFunctions
    {
        internal SystemFunctions(MathRuntime runtime)
        {
            Runtime = runtime;
        }

        #region Public Properties

        public MathRuntime Runtime { get; private set; }

        #endregion

        #region Math Formulas

        #region Basic Math Functions

        /// <summary>
        /// Gets the absolute value of the value
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Absolute value of the value provideds</returns>
        [SystemFunction("abs")]
        [Parameter("value", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Abs(MathValue value)
        {
            var numValue = value.ConvertToNumber();
            return Runtime.NewNumber(Math.Abs((double)numValue.Value));
        }

        /// <summary>
        /// Gets the next highest integer greater than the value
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>The next integer higher than the value</returns>
        [SystemFunction("ceiling")]
        [Parameter("value", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Ceiling(MathValue value)
        {
            var numValue = value.ConvertToNumber();
            return Runtime.NewNumber(Math.Ceiling((double)numValue.Value));
        }

        /// <summary>
        /// Gets the next lowest integer less than the value
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>The next lowest integer than the value</returns>
        [SystemFunction("floor")]
        [Parameter("value", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Floor(MathValue value)
        {
            var numValue = value.ConvertToNumber();
            return Runtime.NewNumber(Math.Floor((double)numValue.Value));
        }

        /// <summary>
        /// Gets the Logorithm of the first value in the base of the second value, or base 10
        /// if no value is provided.
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Logorithm of the first value in the base provided</returns>
        [SystemFunction("log")]
        [Parameter("value", ParameterType = ValueTypes.AllNumeric, Ordinal = 0)]
        [Parameter("basen", ParameterType = ValueTypes.AllNumeric, DefaultValue = "10", Ordinal = 1)]
        public MathValue Log(MathValue value, MathValue basen=null)
        {
            var numValue = value.ConvertToNumber();
            var baseValue = (basen==Runtime.Void || basen==null)
                ? Runtime.NewNumber(10)
                : basen.ConvertToNumber();
            var b = (double)baseValue.Value;
            return Runtime.NewNumber(Math.Log((double)numValue.Value,b));
        }

        /// <summary>
        /// Gets the natural log value of the value
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Natural log value of the value provided</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Naming", 
            "CA1709:IdentifiersShouldBeCasedCorrectly", 
            MessageId = "Ln",
            Justification="Ln is the correct name for a natural logorithm"
            )]
        [SystemFunction("ln")]
        [Parameter("value", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Ln(MathValue value)
        {
            var numValue = value.ConvertToNumber();
            return Runtime.NewNumber(Math.Log((double)numValue.Value));
        }

        /// <summary>
        /// Returns the n root of the value
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Absolute value of the value provideds</returns>
        [SystemFunction("root")]
        [Parameter("value", ParameterType = ValueTypes.AllNumeric, Ordinal = 0)]
        [Parameter("basen", ParameterType = ValueTypes.AllNumeric, DefaultValue = "2", Ordinal = 1)]
        public MathValue Root(MathValue value,MathValue basen = null)
        {
            
            var baseValue = (basen == Runtime.Void || basen==null)
                ? basen.ConvertToNumber()
                : Runtime.NewNumber(2);
            var numValue =  value.ConvertToNumber();
            var r = (double)baseValue.Value;

            return Runtime.NewNumber(Math.Pow((double)numValue.Value, 1.0/r));
        }

        /// <summary>
        /// Rounds the number to the specified number of decimal places, if no place value is
        /// provided, rounds the number to zero decimal places
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Absolute value of the value provideds</returns>
        [SystemFunction("round")]
        [Parameter("value", ParameterType = ValueTypes.AllNumeric, Ordinal = 0)]
        [Parameter("places", ParameterType = ValueTypes.AllNumeric, DefaultValue = "0", Ordinal = 1)]
        public MathValue Round(MathValue value,MathValue places=null)
        {
            var numValue = value.ConvertToNumber();
            var baseValue = (places==Runtime.Void || places==null)
                ? Runtime.Zero
                : places.ConvertToNumber();
            int p = (int)Math.Truncate((double) baseValue.Value);
            
            return Runtime.NewNumber(Math.Round((double)numValue.Value,p));
        }

        /// <summary>
        /// Gets the square root value of the value
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Square root value of the value provideds</returns>
        [SystemFunction("sqrt")]
        [Parameter("value", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Sqrt(MathValue value)
        {
            var numValue = value.ConvertToNumber();
            return Runtime.NewNumber(Math.Sqrt((double)numValue.Value));
        }

        /// <summary>
        /// Either trims the decimal places from a number, or trims whitespace from a string
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns></returns>
        [SystemFunction("trim")]
        [Parameter("value", ParameterType = ValueTypes.BuiltIn)]
        public MathValue Trim(MathValue value)
        {
            var input = value;
            if (input.Type == ValueTypes.String)
            {
                var str = (string)input.Value;
                return Runtime.NewString(str.Trim());
            }
            else
            {
                var numValue = input.ConvertToNumber();
                return Runtime.NewNumber(Math.Truncate((double) numValue.Value));
            }
        }

        #endregion

        #region Trigonometric Functions

        /// <summary>
        /// Gets the arc-cosine of the parameter in radians
        /// </summary>
        /// <param name="radians"></param>
        /// <returns>arccos of the value in radians</returns>
        [SystemFunction("acos")]
        [Parameter("angle",ParameterType=ValueTypes.AllNumeric)]
        public MathValue Acos(MathValue angle)
        {
            var numValue = angle.ConvertToNumber();
            return Runtime.NewNumber(Math.Acos((double)numValue.Value));
        }

        /// <summary>
        /// Gets the arc-secant of the parameter in radians
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>arcsec of the value in radians</returns>
        [SystemFunction("asin")]
        [Parameter("angle", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Asin(MathValue angle)
        {
            var numValue = angle.ConvertToNumber();
            return Runtime.NewNumber(Math.Asin((double)numValue.Value));
        }

        

        /// <summary>
        /// Gets the cosine of the parameter in radians
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Cosine of the value in radians</returns>
        [SystemFunction("cos")]
        [Parameter("radians", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Cos(MathValue radians)
        {
            var numValue = radians.ConvertToNumber();
            return Runtime.NewNumber(Math.Cos((double)numValue.Value));
        }


        /// <summary>
        /// Gets the hyperbolic cosine of the parameter in radians
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Hyperbolic Cosine of the value in radians</returns>
        [SystemFunction("cosh")]
        [Parameter("radians", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Cosh(MathValue radians)
        {
            var numValue = radians.ConvertToNumber();
            return Runtime.NewNumber(Math.Cosh((double)numValue.Value));
        }

        /// <summary>
        /// Gets the sine of the parameter in radians
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Sine of the value in radians</returns>
        [SystemFunction("sin")]
        [Parameter("radians", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Sin(MathValue radians)
        {
            var numValue = radians.ConvertToNumber();
            return Runtime.NewNumber(Math.Sin((double)numValue.Value));
        }

        /// <summary>
        /// Gets the hyperbolic sine of the parameter in radians
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>sinh of the value in radians</returns>
        [SystemFunction("sinh")]
        [Parameter("radians", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Sinh(MathValue radians)
        {
            var numValue = radians.ConvertToNumber();
            return Runtime.NewNumber(Math.Sinh((double)numValue.Value));
        }

        /// <summary>
        /// Gets the tangent of the parameter in radians
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Tangent of the value in radians</returns>
        [SystemFunction("tan")]
        [Parameter("radians", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Tan(MathValue radians)
        {
            var numValue = radians.ConvertToNumber();
            return Runtime.NewNumber(Math.Tan((double)numValue.Value));
        }

        /// <summary>
        /// Gets the arctangent of the parameter in radians
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>atan of the value in radians</returns>
        [SystemFunction("atan")]
        [Parameter("angle", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Atan(MathValue angle)
        {
            var numValue = angle.ConvertToNumber();
            return Runtime.NewNumber(Math.Atan((double)numValue.Value));
        }

        /// <summary>
        /// Gets the hyperbolic tangent of the parameter in radians
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>tanh of the value in radians</returns>
        [SystemFunction("tanh")]
        [Parameter("radians", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Tanh(MathValue radians)
        {
            var numValue = radians.ConvertToNumber();
            return Runtime.NewNumber(Math.Tanh((double)numValue.Value));
        }

        /// <summary>
        /// Converts radians to radians
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Result in radians</returns>
        [SystemFunction("rad")]
        [Parameter("degrees", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Rad(MathValue degrees)
        {
            var numValue = degrees.ConvertToNumber(); 
            return Runtime.NewNumber(ToRadians((double)numValue.Value));
        }

        /// <summary>
        /// Converts radians to radians
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Result in radians</returns>
        [SystemFunction("deg")]
        [Parameter("radians", ParameterType = ValueTypes.AllNumeric)]
        public MathValue Deg(MathValue radians)
        {
            var numValue = radians.ConvertToNumber();
            return Runtime.NewNumber(ToDegrees((double)numValue.Value));
        }

        /// <summary>
        /// Used internally to convert radians to degrees
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        private double ToDegrees(double x)
        {
            return x * (180.0 / Math.PI);
        }

        /// <summary>
        /// Used internally to convert degrees to radians
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        private double ToRadians(double x)
        {
            return Math.PI * x / 180.0;
        }

        #endregion

        #region Set Functions

        /// <summary>
        /// Gets The average of numeric values in a set
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Numeric average of a set</returns>
        [SystemFunction("avg")]
        [Parameter("set",ParameterType=ValueTypes.Set)]
        public MathValue Avg(MathValue set)
        {
            var value = (SetValue)set.ConvertToSet();
            var dataPoints = value.Results.Select(v => (double) v.ConvertToNumber().Value).ToArray();
            var result = dataPoints.Average();
            return Runtime.NewNumber(result);
        }

        /// <summary>
        /// Another definition for average
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Numeric average of a set</returns>
        [SystemFunction("mean")]
        [Parameter("set", ParameterType = ValueTypes.Set)]
        public MathValue Mean(MathValue set)
        {
            return Avg(set);
        }

        /// <summary>
        /// Gets the max value from the set
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Largest mathematical value in the set</returns>
        [SystemFunction("max")]
        [Parameter("set", ParameterType = ValueTypes.Set)]
        public MathValue Max(MathValue set)
        {
            var value = (SetValue)set.ConvertToSet();
            return value.Results.Max();
        }

        /// <summary>
        /// Gets the median value from the set
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Median value of a set</returns>
        [SystemFunction("median")]
        [Parameter("set", ParameterType = ValueTypes.Set)]
        public MathValue Median(MathValue set)
        {
            return Percentile(set, Runtime.NewNumber(50));
        }

        /// <summary>
        /// Gets the minimum value from the set
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Min value of a set</returns>
        [SystemFunction("min")]
        [Parameter("set", ParameterType = ValueTypes.Set)]
        public MathValue Min(MathValue set)
        {
            var value = (SetValue)set.ConvertToSet();
            return value.Results.Min();
        }

        /// <summary>
        /// Gets the most common repeated value from a set
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Median value of a set</returns>
        [SystemFunction("mode")]
        [Parameter("set", ParameterType = ValueTypes.Set)]
        public MathValue Mode(MathValue set)
        {
            var value = (SetValue)set.ConvertToSet();
            var distinctValues = value.Results.Distinct().ToArray();
            var maxCount = 0;
            MathValue maxValue = null;
            foreach (var d in distinctValues)
            {
                var count = value.Results.Count(v => v.Equals(d));
                if (count>maxCount)
                {
                    maxCount = count;
                    maxValue = d;
                }
            }
            return maxValue;
        }

        /// <summary>
        /// Gets the standard deviation value from the set
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>Std. dev. value of a set</returns>
        [SystemFunction("stddev")]
        [Parameter("set", ParameterType = ValueTypes.Set)]
        public MathValue StdDev(MathValue set)
        {
            var value = (SetValue)set.ConvertToSet();
            var mean = (double)Mean(value).Value;
            var diffs = value.Results.Select(v => ((double) v.ConvertToNumber().Value) - mean).ToArray();
            var diffSq = diffs.Select(v => v*v);
            var avgDiff = diffSq.Average();
            var stdDev = Math.Sqrt(avgDiff);

            return Runtime.NewNumber(stdDev);
        }

       
        /// <summary>
        /// Returns a new list of sorted values in accending order
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>A new sorted set</returns>
        [SystemFunction("sort")]
        [Parameter("set", ParameterType = ValueTypes.Set)]
        public MathValue Sort(MathValue set)
        {
            var value = (SetValue)set.ConvertToSet();
            var result = value.Results.OrderBy(v => v.Value).ToArray();
            return Runtime.NewSet(result);
        }

        /// <summary>
        /// Returns a new set of storted values in decending order
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>A new set of sorted values</returns>
        [SystemFunction("sortr")]
        [Parameter("set", ParameterType = ValueTypes.Set)]
        public MathValue Sortr(MathValue set)
        {
            var value = (SetValue)set.ConvertToSet();
            var result = value.Results.OrderByDescending(v => v.Value).ToArray();
            return Runtime.NewSet(result);
        }

        /// <summary>
        /// Gets the p percentile of the set
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>p percentile of a set</returns>
        [SystemFunction("percentile")]
        [Parameter("set", ParameterType = ValueTypes.Set, Ordinal = 0)]
        [Parameter("p", ParameterType = ValueTypes.Number, Ordinal = 1)]
        public MathValue Percentile(MathValue set,MathValue p)
        {
            var sorted = (SetValue)Sort(set);
            var values = sorted.Results.ToArray();

            var percent = (double)p.Value / 100.0;
            double listCount = values.Length + 1;
            var r = percent * listCount;
            var i = (int)Math.Floor(r);
            var d = r - i;
            var iValue = (double)values[i - 1].ConvertToNumber().Value;
            var nextValue = (double)values[i].ConvertToNumber().Value;
            var diff = nextValue - iValue;
            var interp = d * diff;
            
            return Runtime.NewNumber(iValue + interp);
        }

        #endregion

        #region Data Type Functions

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is a bool
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is a bool</returns>
        [SystemFunction("isbool")]
        [Parameter("value")]
        public MathValue IsBool(MathValue value)
        {
            return Runtime.NewBool(value.Type == ValueTypes.Bool);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is a complex
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is a complex</returns>
        [SystemFunction("iscomplex")]
        [Parameter("value")]
        public MathValue IsComplex(MathValue value)
        {
            return Runtime.NewBool(value.Type == ValueTypes.Complex);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter converts to false
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value can be converted to false</returns>
        [SystemFunction("isfalse")]
        [Parameter("value")]
        public MathValue IsFalse(MathValue value)
        {
            var boolValue = value.ConvertToBoolean();
            return Runtime.NewBool(!(bool)boolValue.Value);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is an imaginary value
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is imaginary</returns>
        [SystemFunction("isimaginary")]
        [Parameter("value")]
        public MathValue IsImaginary(MathValue value)
        {
            var complex = value as ComplexValue;
            if (complex == null)
                return Runtime.False;

            return complex.I.ConvertToBoolean();
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is a negative value
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is negative</returns>
        [SystemFunction("isnegative")]
        [Parameter("value")]
        public MathValue IsNegative(MathValue value)
        {
            return value != Abs(value);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is a number
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is a number</returns>
        [SystemFunction("isnumber")]
        [Parameter("value")]
        public MathValue IsNumber(MathValue value)
        {
            return Runtime.NewBool(value.Type == ValueTypes.Number);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is a positive value
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is positive</returns>
        [SystemFunction("ispositive")]
        [Parameter("value")]
        public MathValue IsPositive(MathValue value)
        {
            return value == Abs(value);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is a range value
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is a range</returns>
        [SystemFunction("isrange")]
        [Parameter("value")]
        public MathValue IsRange(MathValue value)
        {
            return Runtime.NewBool(value.Type == ValueTypes.Range);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is a set
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is a set</returns>
        [SystemFunction("isset")]
        [Parameter("value")]
        public MathValue IsSet(MathValue value)
        {
            return Runtime.NewBool(value.Type == ValueTypes.Set);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter converts to true
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value can be converted to true</returns>
        [SystemFunction("istrue")]
        [Parameter("value")]
        public MathValue IsTrue(MathValue value)
        {
            var boolValue = value.ConvertToBoolean();
            return Runtime.NewBool((bool)boolValue.Value);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is a unit
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is a unit</returns>
        [SystemFunction("isunit")]
        [Parameter("value")]
        public MathValue IsUnit(MathValue value)
        {
            return Runtime.NewBool(value.Type == ValueTypes.Unit);
        }

        /// <summary>
        /// Gets a string representing the data type of the parameter proided
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>String name of the data type</returns>
        [SystemFunction("type")]
        [Parameter("value")]
        public MathValue Type(MathValue value)
        {
            return Runtime.NewString(value.TypeName);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is a void
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is a void</returns>
        [SystemFunction("isvoid")]
        [Parameter("value")]
        public MathValue IsVoid(MathValue value)
        {
            return Runtime.NewBool(value.Type == ValueTypes.Void);
        }

        /// <summary>
        /// Gets a BooleanValue indicating if the provided parameter is a string
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>True if the value is a string</returns>
        [SystemFunction("isstring")]
        [Parameter("value")]
        public MathValue IsString(MathValue value)
        {
            return Runtime.NewBool(value.Type == ValueTypes.String);
        }

        ///// <summary>
        ///// Gets a BooleanValue indicating if the provided parameter is an expression
        ///// </summary>
        ///// <param name="parameters">Generic input bindings for system functions</param>
        ///// <returns>True if the value is an expression</returns>
        //[SystemFunction("isexpression")]
        //[Parameter("value")]
        //public MathValue IsExpression(MathValue value)
        //{
        //    return Runtime.NewBool(value.Type == ValueTypes.Expression);
        //}

        /// <summary>
        /// Converts the provided parameter to a number
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>NumberValue of the parameter</returns>
        [SystemFunction("tonumber")]
        [Parameter("value")]
        public MathValue ToNumber(MathValue value)
        {
            return value.ConvertToNumber();
        }

        /// <summary>
        /// Converts the provided parameter to a bool
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>BooleanValue of the parameter</returns>
        [SystemFunction("tobool")]
        [Parameter("value")]
        public MathValue ToBool(MathValue value)
        {
            return value.ConvertToBoolean();
        }

        /// <summary>
        /// Converts the provided parameter to a bool
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>BooleanValue of the parameter</returns>
        [SystemFunction("tocomplex")]
        [Parameter("value")]
        public MathValue ToComplex(MathValue value)
        {
            return value.ConvertToComplex();
        }

        /// <summary>
        /// Converts the provided parameter to a string
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>StringValue of the parameter</returns>
        [SystemFunction("tostring")]
        [Parameter("value")]
        public MathValue ToString(MathValue value)
        {
            return value.ConvertToString();
        }

        /// <summary>
        /// Converts the provided parameter to a range (with a spread of zero)
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>RangeValue of the parameter</returns>
        [SystemFunction("torange")]
        [Parameter("lower",Ordinal=0)]
        [Parameter("upper", Ordinal = 1)]
        public MathValue ToRange(MathValue lower, MathValue upper = null)
        {
            if (upper == null)
            {
                var number = (NumberValue) lower.ConvertToNumber();
                return Runtime.NewRange(number);
            }


            var l = (NumberValue) lower.ConvertToNumber();
            var u = (NumberValue) upper.ConvertToNumber();
            return Runtime.NewRange(l, u);

        }

        /// <summary>
        /// Converts the provided parameter to a set (with only one element)
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>SetValue of the parameter</returns>
        [SystemFunction("toset")]
        [Parameter("value")]
        public MathValue ToSet(MathValue value)
        {
            return Runtime.NewSet(value);
        }

        /// <summary>
        /// Converts the provided parameter to a unit based on the unit type of the second parameter
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>UnitValue of the first parameter in terms of the second parameter</returns>
        [SystemFunction("tounit")]
        [Parameter("value")]
        [Parameter("measure")]
        public MathValue ToUnit(MathValue value,UnitMeasure measure)
        {
            var num = (NumberValue)value.ConvertToNumber();
            return Runtime.NewUnit(num, measure);
        }

        #endregion

        #region String Functions

        /// <summary>
        /// Returns the length of a string as a NumberValue
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>NumberValue representing the length of the string</returns>
        [SystemFunction("length")]
        [Parameter("value")]
        public MathValue Length(MathValue value)
        {
            var set = value as SetValue;
            if (set!=null)
            {
                return Runtime.NewNumber(set.Count());
            }

            var stringValue =value.ConvertToString();
            return Runtime.NewNumber(((string)stringValue.Value).Length);
        }

        /// <summary>
        /// Reverses a string and returns the results
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>StringValue with the reversed data of the parameter</returns>
        [SystemFunction("reverse")]
        [Parameter("value")]
        public MathValue Reverse(MathValue value)
        {
            var set = value as SetValue;
            if (set!=null)
            {
                return Runtime.NewSet(set.Reverse());
            }

            var stringValue = value.ConvertToString();
            return Runtime.NewString(new String(((string)stringValue.Value).Reverse().ToArray()));
        }

        #endregion

        #region Introspective Function Definitions

        /// <summary>
        /// Returns if the language element is defined
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>A BooleanValue indicating if the value has been defined (or is in scope)</returns>
        [SystemFunction("isdef")]
        [Parameter("symbol")]
        public MathValue IsDef(MathValue symbol)
        {
            var value = symbol as SymbolValue;
            if (value == null)
                throw new MathException(ErrorCodes.SymbolExpected, Resources.SymbolExpected);

            throw new NotImplementedException();
        }

        /// <summary>
        /// Lists all domains available in the provided domain.  If the provided domain
        /// is empty, it returnes the child domains of the current domain.  Only named domains will be returned
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>StringValue with a list of domains</returns>
        [SystemFunction("domains")]
        [Parameter("symbol")]
        public MathValue Domains(MathValue symbol)
        {
            var set = new List<MathValue>();
            var domain = FindDomain(symbol);

            foreach (var v in domain.AllDomains)
            {
                set.Add(new SymbolValue(v, Runtime));
            }

            return Runtime.NewSet(set);
        }

        /// <summary>
        /// Lists all child domains within the provided local domain.  If the provided domain
        /// is empty, it returnes the child domains of the current domain
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>SetValue with a list of domains</returns>
        [SystemFunction("domainsl")]
        [Parameter("symbol")]
        public MathValue DomainsL(MathValue symbol)
        {
            var set = new List<MathValue>();
            var domain = FindDomain(symbol);

            foreach (var v in domain.LocalDomains)
            {
                set.Add(new SymbolValue(v, Runtime));
            }

            return Runtime.NewSet(set);
        }

        /// <summary>
        /// Lists all functions available in the provided domain.  If the provided domain
        /// is empty, it returnes the functions available to the current domain
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>SetValue with a list of functions</returns>
        [SystemFunction("functions")]
        [Parameter("symbol")]
        public MathValue Functions(MathValue symbol)
        {
            var set = new List<MathValue>();
            var domain = FindDomain(symbol);

            foreach (var v in domain.AllFunctions)
            {
                set.Add(new SymbolValue(v, Runtime));
            }

            return Runtime.NewSet(set);
        }

        /// <summary>
        /// Lists all functions decleared locally in the provided domain.  If the provided domain
        /// is empty, it returnes the functions decleared in the current domain
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>StringValue with a list of functions</returns>
        [SystemFunction("functionsl")]
        [Parameter("symbol")]
        public MathValue FunctionsL(MathValue symbol)
        {
            var set = new List<MathValue>();
            var domain = FindDomain(symbol);

            foreach (var v in domain.LocalFunctions)
            {
                set.Add(new SymbolValue(v, Runtime));
            }

            return Runtime.NewSet(set);
        }

        /// <summary>
        /// Lists all variables available in the provided domain.  If the provided domain
        /// is empty, it returnes the variables available to the current domain
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>StringValue with a list of variables</returns>
        [SystemFunction("variables")]
        [Parameter("symbol")]
        public MathValue Variables(MathValue symbol)
        {
            var set = new List<MathValue>();
            var domain = FindDomain(symbol);

            foreach (var v in domain.AllVariables)
            {
                set.Add(new SymbolValue(v, Runtime));
            }

            return Runtime.NewSet(set);
        }

        /// <summary>
        /// Lists all variables declared locally in the provided domain.  If the provided domain
        /// is empty, it returnes the functions available to the current domain
        /// </summary>
        /// <param name="parameters">Generic input bindings for system functions</param>
        /// <returns>StringValue with a list of variables</returns>
        [SystemFunction("variablesl")]
        [Parameter("symbol")]
        public MathValue VariablesL(MathValue symbol)
        {
            var set = new List<MathValue>();
            var domain = FindDomain(symbol);

            foreach (var v in domain.LocalVariables)
            {
                set.Add(new SymbolValue(v, Runtime));
            }

            return Runtime.NewSet(set);
        }

        /// <summary>
        ///  Helper method to drill down and locate domains
        /// </summary>
        /// <param name="parameters"></param>
        /// <param name="builder"></param>
        /// <returns></returns>
        private Domain FindDomain(MathValue symbol)
        {
            var value = symbol as SymbolValue;
            if (value == null)
                throw new MathException(ErrorCodes.SymbolExpected, Resources.SymbolExpected);

            var result = value.Value as Domain;
            if (result == null)
                throw new MathException(ErrorCodes.ExpectedFunctionOrDomain, Resources.ExpectedFunctionOrDomain);
            return result;
        }

        #endregion

        #endregion

    }
}
